package com.wh1200.mybatis.xmlless.resolver

import com.wh1200.kotlin.toCamelCase
import com.wh1200.kotlin.toUnderlineCase
import com.wh1200.mybatis.xmlless.dialect.DialectFactory
import com.wh1200.mybatis.xmlless.model.*
import com.wh1200.mybatis.xmlless.model.Properties
import com.wh1200.mybatis.xmlless.model.component.ReplacePropertyToColumnVisitor
import com.wh1200.mybatis.xmlless.model.exception.BuildSQLException
import net.sf.jsqlparser.expression.Function
import net.sf.jsqlparser.parser.CCJSqlParserUtil
import java.util.*

/**
 * Created by 吴昊 on 2018/12/12.
 */
object ColumnsResolver {

  private val functions = listOf("min", "max", "distinct", "sum", "avg")
  val FUNCTION_PATTERN = "(min|max|distinct|sum|avg)\\((\\w+)\\)".toRegex()

  /**
   * 构建查询的列
   */
  fun resolveSelectColumns(
    properties: Properties,
    methodInfo: MethodInfo
  ): List<SelectColumn> {
    return resolveColumns(properties, methodInfo).sortedBy { it.toSql() }
  }

  fun resolveIncludedTables(
    properties: Properties,
    methodInfo: MethodInfo
  ): List<String> {
    val columns = resolveColumns(properties, methodInfo)
    return (columns.map { it.middleTables }.flatten() + columns.mapNotNull {
      it.table
    }).map { it.getAliasOrName() }.distinct()
  }

  fun resolveJoinOnColumn(
    targetEntityName: String,
    joinOnProperty: String,
    joinEntityInfo: XmllessTableInfo
  ): String {
    if (joinOnProperty.isBlank() || joinOnProperty == joinEntityInfo.keyProperty) {
      return joinEntityInfo.keyColumn!!
    }
    return joinEntityInfo.tableFields.find { it.property == joinOnProperty }?.column
      ?: error("无法解析${targetEntityName}的属性${joinOnProperty}对应的数据库字段")
  }

  /**
   * 根据属性名称获取数据库表的列名称，返回的列名称包含表名称
   */
  fun resolveColumnByPropertyName(
    property: String,
    methodInfo: MethodInfo,
    forHaving: Boolean = false,
    forceUseModelMappings: Boolean = false
  ): List<SelectColumn> {
    if (!forceUseModelMappings && methodInfo.propertiesMapping[property] != null) {
      return resolveColumnByPropertyName(methodInfo.propertiesMapping[property]!!, methodInfo, forHaving)
    }
    val mappings = methodInfo.mappings
    // 如果属性中存在.则该属性表示一个关联对象的属性，例如student.subjectId
    // 目前仅支持一层关联关系
    if (property.contains(".")) {
      val columns = resolveColumnByJoinedPropertyName(property, methodInfo)
      if (columns.isNotEmpty()) {
        return columns
      }
      val jsonColumn = resolveColumnFromJsonValue(property, methodInfo)
      if (jsonColumn != null) {
        return listOf(jsonColumn)
      }
    }

    // 匹配持久化对象的属性查找列名
    val columnMapping = resolveFromFieldInfo(property, methodInfo)
    if (columnMapping != null) {
      if (forHaving) {
        return listOf(SelectColumn(columnMapping.fieldWrapper, null, columnMapping.column, property))
      }
      return listOf(
        SelectColumn(
          columnMapping.fieldWrapper, TableName(mappings.tableName),
          columnMapping.column, property
        )
      )
    }
    if (property == "id" && methodInfo.tableInfo.keyColumn != null) {
      return listOf(
        SelectColumn(
          methodInfo.tableInfo.tableFieldMap[methodInfo.tableInfo.keyProperty]!!.fieldWrapper,
          null,
          methodInfo.tableInfo.keyColumn,
          methodInfo.tableInfo.keyProperty!!
        )
      )
    }
    // 从关联属性中匹配
    val resolvedFromJoinProperty = resolveFromPropertyJoinInfo(mappings.mappings, property)
    if (resolvedFromJoinProperty != null) {
      return listOf(resolvedFromJoinProperty)
    }

    // 从关联对象中匹配
    val resolvedFromJoinObject = resolveFromObjectJoinInfo(mappings.mappings, property)
    if (resolvedFromJoinObject.isNotEmpty()) {
      return resolvedFromJoinObject
    }
    val resolvedFromModelJoinObject = resolveFromObjectJoinInfo(methodInfo.modelMappings.mappings, property)
    if (resolvedFromModelJoinObject.isNotEmpty()) {
      return resolvedFromModelJoinObject
    }
    if (methodInfo.mappings != methodInfo.modelMappings) {
      val mapping = methodInfo.mappings.mappings.find { it.property == property && it.joinInfo == null && !it.isCount }
      if (mapping != null) {
        return listOf(
          SelectColumn(mapping.fieldWrapper, null, mapping.column, mapping.property)
        )
      }
    }
    if (property.matches(FUNCTION_PATTERN)) {
      val groups = FUNCTION_PATTERN.find(property)!!.groups
      val fnName = groups[1]!!.value
      val realProperty = groups[2]!!.value
      val actualCol = resolveColumnByPropertyName(
        realProperty, methodInfo, forceUseModelMappings = forceUseModelMappings
      ).single()
      return listOf(
        SelectColumn(null, null, "${fnName}(${actualCol.column})", actualCol.alias)
      )
    }

    if (property.contains("(")) {
      val expression = CCJSqlParserUtil.parseExpression(property)
      if (expression is Function) {
        expression.accept(
          ReplacePropertyToColumnVisitor(methodInfo)
        )
        return listOf(
          SelectColumn(null, null, expression.toString(), null)
        )
      }
    }
    if (FUNCTION_PATTERN.matches(property)) {
      val groups = FUNCTION_PATTERN.find(property)!!.groups
      val fnName = groups[1]!!.value
      val realProperty = groups[2]!!.value
      val actualCol = resolveColumnByPropertyName(
          realProperty, methodInfo, forHaving
      ).single()
      return listOf(
          SelectColumn(actualCol.fieldWrapper, actualCol.table, actualCol.column, actualCol.alias, fnName)
      )
    }
    val fn = functions.find { property.startsWith(it) && property != it }
    if (fn != null) {
      val actualCol = resolveColumnByPropertyName(
        property.drop(fn.length).trim().toCamelCase(),
        methodInfo, forHaving
      ).single()
      return listOf(
        SelectColumn(actualCol.fieldWrapper, actualCol.table, actualCol.column, actualCol.alias, fn)
      )
    }
    if (property.startsWith("$")) {
      return listOf(
        SelectColumn(null, null, "\${${property.drop(1)}}", null)
      )
    }
    throw BuildSQLException("无法解析持久化类【${mappings.modelClassName}】的属性【${property}】对应的列名称, 持久化类或关联对象中不存在此属性")
  }

  private fun resolveColumnFromJsonValue(property: String, methodInfo: MethodInfo): SelectColumn? {
    val parentProperty = property.split(".").first()
    val parentMapping = methodInfo.mappings.mappings.find { it.property == parentProperty } ?: return null
    if (!parentMapping.isJsonObject) {
      return null
    }
    val relativePath = property.split(".").drop(1).joinToString(".") {
      if (it.startsWith("$")) {
        "\${${it.drop(1)}}"
      } else {
        it
      }
    }
    return SelectColumn(
      parentMapping.fieldWrapper, TableName(methodInfo.mappings.tableName),
        DialectFactory.getDialect().toJsonPath(parentMapping, relativePath), null
    )
  }

  private fun resolveColumnByJoinedPropertyName(property: String, methodInfo: MethodInfo): List<SelectColumn> {
    val joinedPropertyWords = property.split(".")
    if (joinedPropertyWords.size <= 2) {
      val objectProperty = joinedPropertyWords[0]
      val joinProperty = joinedPropertyWords[1]
      val col = findFromMappings(methodInfo.mappings.mappings, objectProperty, joinProperty)
        ?: findFromMappings(methodInfo.modelMappings.mappings, objectProperty, joinProperty)
      if (col != null) {
        return listOf(col)
      } else {
        val originCol = findFromMappings(methodInfo.modelMappings.mappings, objectProperty, joinProperty)
        if (originCol != null) {
          return listOf(originCol)
        }
      }
      return listOf()
    }
    return resolveDeepColumnByJoinedPropertyName(joinedPropertyWords, methodInfo, property)
  }

  private fun resolveDeepColumnByJoinedPropertyName(
    joinedPropertyWords: List<String>,
    methodInfo: MethodInfo,
    property: String
  ): List<SelectColumn> {
    val joinOriginProperty = joinedPropertyWords[0]
    val joinOriginMapping = methodInfo.mappings.mappings.find { it.property == joinOriginProperty }
    if (joinOriginMapping != null && joinOriginMapping.isJsonObject) {
      return listOf()
    }
    val joinProperty = joinedPropertyWords.last()
    var objectProperty: String
    var mappings = methodInfo.mappings.mappings
    val mappingList = mutableListOf<Any>()
    joinedPropertyWords.dropLast(1).forEach {
      objectProperty = it
      val mapping = (mappings + methodInfo.modelMappings.mappings).find { fieldMapping ->
        (fieldMapping.joinInfo is ObjectJoinInfo && fieldMapping.property == objectProperty)
            || fieldMapping.joinObjects.any { objectInfo -> objectInfo.alias == objectProperty }
      } ?: error("无法解析属性$property")
      mappings = if (mapping.joinInfo is ObjectJoinInfo) {
        mappingList.add(mapping.joinInfo)
        MappingResolver.getMappingCache(mapping.joinInfo.joinEntityName)!!.mappings
      } else {
        mappingList.add(mapping.joinObjects.first { it.alias == objectProperty })
        MappingResolver.getMappingCache(mapping.joinObjects.first { it.alias == objectProperty }
          .getJoinTableInfo()).mappings
      }
    }
    val finalMapping = mappings.find { it.property == joinedPropertyWords.last() } ?: error("无法解析属性$property")
    val middleTables = mappingList.map { tmp ->
      val tmpList = mappingList.subList(0, mappingList.indexOf(tmp) + 1)
      val tableName = when (val tmpLast = tmpList.last()) {
        is ObjectJoinInfo -> tmpLast.joinTable.name
        is JoinObjectInfo -> tmpLast.getTableName().name
        else -> error("无法解析属性$property")
      }
      val alias = tmpList.joinToString("_") {
        when (it) {
          is ObjectJoinInfo -> it.joinTable.alias
          is JoinObjectInfo -> it.alias
          else -> error("无法解析属性$property")
        }
      }
      TableName(tableName, alias)
    }
    val finalAlias = mappingList.joinToString("_") {
      when (it) {
        is ObjectJoinInfo -> it.joinTable.alias
        is JoinObjectInfo -> it.alias
        else -> error("无法解析属性$property")
      }
    }
    val last = mappingList.last()
    if (last is ObjectJoinInfo) {
      val joinTable = TableName(last.joinTable.name, finalAlias)
      return listOf(
        SelectColumn(
          finalMapping.fieldWrapper,
          joinTable,
          finalMapping.column,
          if (last.associationPrefix.isNotBlank()) {
            last.associationPrefix + joinProperty.toUnderlineCase()
              .lowercase(Locale.getDefault())
          } else {
            null
          },
          middleTables = middleTables
        )
      )
    }
    last as JoinObjectInfo
    val joinTable = TableName(last.getTableName().name, finalAlias)
    return listOf(
      SelectColumn(
        finalMapping.fieldWrapper,
        joinTable,
        finalMapping.column,
        finalMapping.property,
        middleTables = middleTables
      )
    )
  }

  /**
   * 获取select查询的列名称语句
   */
  private fun resolveColumns(
    properties: Properties,
    methodInfo: MethodInfo
  ): List<SelectColumn> {
    if (properties.isIncludeNotEmpty()) {
      return properties.includes.map { property ->
        if (property in methodInfo.propertiesMapping) {
          val exp = ExpressionResolver.parseExpression(methodInfo.propertiesMapping[property]!!, methodInfo)
          listOf(
            SelectColumn(
              null,
              null, exp.first,
              property.toUnderlineCase().lowercase(),
              middleTables = exp.second
            )
          )
        } else {
          resolveColumnByPropertyName(property, methodInfo)
        }
      }.flatten()
    }
    return methodInfo.mappings.mappings.asSequence().filter {
      !it.selectIgnore && it.property !in properties.excludes
    }.map { mapping ->
      when {
        mapping.property in methodInfo.propertiesMapping -> {
          val exp = ExpressionResolver.parseExpression(methodInfo.propertiesMapping[mapping.property]!!, methodInfo)
          listOf(
            SelectColumn(
              mapping.fieldWrapper,
              null, exp.first, mapping.property,
              middleTables = exp.second
            )
          )
        }

        mapping.joinInfo != null -> {
          mapping.joinInfo.selectFields(1)
        }

        else -> listOf(
          SelectColumn(
            mapping.fieldWrapper,
            TableName(methodInfo.mappings.tableName),
            mapping.column, mapping.property
          )
        )
      }
    }.flatten().filter { it.column.isNotBlank() }.distinctBy { it.toSql() }.toList()
  }

  private fun findFromMappings(
    mappings: List<FieldMapping>,
    objectProperty: String,
    joinProperty: String,
  ): SelectColumn? {
    val it = mappings.firstOrNull {
      it.joinInfo is ObjectJoinInfo && it.property == objectProperty
    } ?: return findInJoinProperty(
      mappings, objectProperty, joinProperty
    )
    return SelectColumn(
      null,
      it.joinInfo!!.joinTable,
      joinProperty.toUnderlineCase().lowercase(Locale.getDefault()),
      if (it.joinInfo is ObjectJoinInfo && it.joinInfo.associationPrefix.isNotBlank()) {
        it.joinInfo.associationPrefix + joinProperty.toUnderlineCase()
          .lowercase(Locale.getDefault())
      } else {
        null
      }
    )
  }

  private fun findInJoinProperty(
    mappings: List<FieldMapping>,
    objectProperty: String,
    joinProperty: String
  ): SelectColumn? {
    mappings.filter {
      it.joinObjects.isNotEmpty()
    }.forEach {
      val joinObject = it.joinObjects.find { joinObjectInfo -> joinObjectInfo.alias == objectProperty }
      if (joinObject != null) {
        joinObject.getJoinTableInfo().tableFieldMap[joinProperty]?.let { fieldInfo ->
          return SelectColumn(
            fieldInfo.fieldWrapper,
            joinObject.getTableName(),
            fieldInfo.column,
            fieldInfo.property
          )
        }
      }
    }
    return null
  }

  /**
   * 从表信息中解析对象属性名称对应的数据库表的列名称
   */
  private fun resolveFromFieldInfo(property: String, methodInfo: MethodInfo): FieldMapping? {
    val column = methodInfo.mappings.mappings.firstOrNull {
      it.joinInfo == null
          && !it.isCount && it.property == property
    }
    if (column != null) {
      return column
    }
    val originColumn = methodInfo.modelMappings.mappings.firstOrNull {
      it.joinInfo == null && it.property == property
    }
    if (originColumn != null) {
      return originColumn
    }
    return null
  }

  private fun resolveFromObjectJoinInfo(
    mappings: List<FieldMapping>, property: String
  ): List<SelectColumn> {
    // 如果持久化对象中找不到这个属性，在关联的对象中进行匹配，匹配的规则是，关联对象的字段名称+属性名称
    // 例如，当前的model是Student，有一个名为scores的属性类型为List<Score>,表示该学生的成绩列表,
    // Score对象中有一个属性subjectId，那么scoresSubjectId可以匹配到关联对象的subjectId属性上
    // 这个适用于主对象和关联对象中存在同名属性，而需要查询关联对象属性或以关联对象属性作为条件判断的情况
    val joinMapping = mappings.firstOrNull {
      it.joinInfo is ObjectJoinInfo && it.property == property
    }
    if (joinMapping != null) {
      return joinMapping.joinInfo!!.selectFields(1)
    }
    val bestObjectJoinMapping = mappings.filter {
      it.joinInfo is ObjectJoinInfo
    }.firstOrNull { property.startsWith(it.property) }
    if (bestObjectJoinMapping != null) {
      val maybeJoinPropertyPascal = property.replaceFirst(bestObjectJoinMapping.property, "")
      if (maybeJoinPropertyPascal.isNotBlank() && maybeJoinPropertyPascal.first() in 'A'..'Z') {
        val maybeJoinProperty = maybeJoinPropertyPascal.toCamelCase()
        return listOf(
          SelectColumn(
            null,
            bestObjectJoinMapping.joinInfo!!.joinTable,
            resolveJoinPropertyColumn(maybeJoinProperty, bestObjectJoinMapping.joinInfo), null
          )
        )
      }
    }
    // 如果持久化对象中找不到这个属性，在关联的对象中匹配同名属性，但是该属性名称必须唯一，即不能在多个关联对象中都存在该名称的属性
    // 例如，当前的model是Student，有一个名为scores的属性类型为List<Score>,表示该学生的成绩列表,
    // Score对象中有一个属性subjectId，那么subjectId可以匹配到关联对象的subjectId属性上
    val matchedJoinInfos = mappings.mapNotNull {
      it.joinInfo
    }.filterIsInstance<ObjectJoinInfo>().filter { joinInfo ->
      joinInfo.joinTableInfo?.tableFields?.firstOrNull { it.property == property } != null
    }
    return when {
      matchedJoinInfos.size > 1 -> throw BuildSQLException(
        "在${matchedJoinInfos.joinToString(",") { it.joinEntityName }}发现了相同的属性$property, " +
            "无法确定属性对应的表及字段"
      )

      matchedJoinInfos.size == 1 -> listOf(
        SelectColumn(
          null,
          matchedJoinInfos.first().joinTable,
          resolveJoinPropertyColumn(property, matchedJoinInfos.first()), null
        )
      )

      else -> listOf()
    }
  }

  private fun resolveJoinPropertyColumn(property: String, joinInfo: JoinInfo): String {
    val mappings = joinInfo.typeInfo.getMappings()
    if (mappings != null) {
      val fieldMapping = mappings.mappings.find { it.property == property }
      if (fieldMapping != null) {
        return fieldMapping.column
      }
    }
    return property.toUnderlineCase().lowercase(Locale.getDefault())
  }

  /**
   * 从关联属性中解析对应的关联表的列名称
   */
  private fun resolveFromPropertyJoinInfo(
    mappings: List<FieldMapping>,
    property: String
  ): SelectColumn? {
    val matchedMapping = mappings.firstOrNull {
      it.joinInfo != null && it.joinInfo is PropertyJoinInfo
          && it.property == property
    }
    if (matchedMapping?.joinInfo != null) {
      val joinInfo = matchedMapping.joinInfo as PropertyJoinInfo
      return SelectColumn(
        null,
        joinInfo.joinTable,
        joinInfo.propertyColumn.name,
        joinInfo.propertyColumn.alias
      )
    }
    mappings.filter { it.joinObjects.isNotEmpty() }.forEach { mapping ->
      mapping.joinObjects.forEach { joinObjectInfo ->
        val newProperty = if (property.startsWith(joinObjectInfo.alias + ".")) {
          property.drop(joinObjectInfo.alias.length + 1)
        } else if (property.startsWith(joinObjectInfo.alias)) {
          property.drop(joinObjectInfo.alias.length).toCamelCase()
        } else {
          null
        }
        if (newProperty != null) {
          val tableInfo = joinObjectInfo.getJoinTableInfo()
          tableInfo.tableFieldMap[newProperty]?.let { fieldInfo ->
            return SelectColumn(
              fieldInfo.fieldWrapper,
              joinObjectInfo.getTableName(),
              fieldInfo.column,
              fieldInfo.property
            )
          }
        }
      }
    }
    return null
  }

}
